﻿using System;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using Radovici.Ctrls.Panels;
using Radovici.SharedLibrary;

namespace Radovici.Ctrls.ContentControls
{
    public class Dragable : ContentControl, IDisposable
    {
        #region Private fields
        private ContentControl _dragableContainerPart;
        private Control _dragablePart;
        private bool _isDragging;
        private Point _previousPosition;
        private int _dragIndex = 0;
        private Canvas _cachedCanvas;
        private static int _dragZIndex = 0; //slightly different than _dragIndex
        #endregion

        public event Action<Dragable> DragStarted;
        public event Action<Dragable, MouseEventArgs> Dragging;
        public event Action<Dragable> DragStoped;

        public Dragable()
        {
            DefaultStyleKey = typeof(Dragable);
        }        

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _dragableContainerPart = this.GetTemplateChild("DragableContainerPart") as ContentControl;
            if (_dragableContainerPart != null)
            {
                _dragablePart = _dragableContainerPart.Content as Control; //this.GetTemplateChild("DragablePart") as Control;
                if (_dragablePart != null)
                {
                    _dragablePart.MouseLeftButtonDown += new MouseButtonEventHandler(OnContentMouseLeftButtonDown);
                    _dragablePart.MouseMove += new MouseEventHandler(OnContentMouseMove);
                    _dragablePart.MouseLeftButtonUp += new MouseButtonEventHandler(OnContentMouseLeftButtonUp);
                }
            }
        }

        protected static int TopMostZIndex
        {
            get { return ++_dragZIndex; }
        }

        #region DragEnabled
        public bool DragEnabled
        {
            get { return (bool)GetValue(DragEnabledProperty); }
            set { SetValue(DragEnabledProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DragEnabled.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DragEnabledProperty = DependencyProperty.Register("DragEnabled", typeof(bool), typeof(Dragable), new PropertyMetadata(true, OnDragEnabledChanged));

        private static void OnDragEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }
        #endregion

        #region IsDragPermanent
        public bool IsDragPermanent
        {
            get { return (bool)GetValue(IsDragPermanentProperty); }
            set { SetValue(IsDragPermanentProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsDragPermanent.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsDragPermanentProperty = DependencyProperty.Register("IsDragPermanent", typeof(bool), typeof(Dragable), new PropertyMetadata(false));
        #endregion

        #region DragBar event handlers
        private void OnContentMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Interlocked.Increment(ref _dragIndex);
            if (DragEnabled && !_isDragging)
            {
                var ctrl = sender as UIElement;
                if (ctrl != null)
                {
                    ctrl.CaptureMouse();
                    _previousPosition = e.GetPosition(ctrl);
                }
                int curDragIndex = _dragIndex;
                new Timer(delegate
                    {
                        Dispatcher.BeginInvoke(delegate
                                                {
                                                    if (curDragIndex == _dragIndex)
                                                    {
                                                        StartDrag();
                                                        Drag(e);
                                                    }
                                                });
                    }, null, 50, int.MaxValue);
            }
            e.Handled = true;
        }

        private void OnContentMouseMove(object sender, MouseEventArgs e)
        {
            if (_isDragging)
            {
                Drag(e);
            }
        }

        private void OnContentMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            Interlocked.Increment(ref _dragIndex);
            if (_isDragging)
            {
                StopDrag();
                var ctrl = sender as UIElement;
                if (ctrl != null)
                {
                    ctrl.ReleaseMouseCapture();
                }
            }
            e.Handled = true;
        }
        #endregion

        #region Protected methods
        protected virtual void OnDragStarted()
        {          
            if (DragStarted != null)
            {
                DragStarted(this);
            }
        }

        protected virtual void OnDragging(MouseEventArgs e)
        {
            if (Dragging != null)
            {
                Dragging(this, e);
            }
        }

        protected virtual void OnDragStopped()
        {     
            if (DragStoped != null)
            {
                DragStoped(this);
            }
        }
        #endregion

        #region Protected methods
        protected Canvas Canvas
        {
            get
            {
                if (_cachedCanvas == null)
                {
                    _cachedCanvas = this.FindParent<Canvas>();
                    if (_cachedCanvas == null)
                    {
                        _cachedCanvas = DragableCanvas.Canvas;
                        if (_cachedCanvas == null)
                        {
                            throw new Exception("Invalid state; no parent canvas for dragging");
                        }
                    }
                }
                return _cachedCanvas;
            }
        }

        public void Popout()
        {
            if (_dragableContainerPart.Content == _dragablePart)
            {
                _dragableContainerPart.Content = null;
                Canvas.Children.Add(_dragablePart);
            }
            GeneralTransform gt = this.TransformToVisual(Canvas);
            Point offset = gt.Transform(new Point(0, 0));
            double controlLeft = offset.X;
            double controlTop = offset.Y;
            Canvas.SetLeft(_dragablePart, controlLeft);
            Canvas.SetTop(_dragablePart, controlTop);
            Canvas.SetZIndex(_dragablePart, TopMostZIndex);
        }        

        public void Popin()
        {
            Popin(true);   
        }

        private void Popin(bool force)
        {
            if ((force || !IsDragPermanent) && Canvas.Children.Contains(_dragablePart))
            {
                Canvas.Children.Remove(_dragablePart);
                _dragableContainerPart.Content = _dragablePart;
            }              
        }
        #endregion

        #region Private methods
        private void StartDrag()
        {            
            //Start drag
            _isDragging = true;
            Popout();
            //Drag started
            OnDragStarted();
        }

        private void Drag(MouseEventArgs e)
        {
            Point position = e.GetPosition(Canvas);
            Canvas.SetLeft(_dragablePart, position.X - _previousPosition.X);
            Canvas.SetTop(_dragablePart, position.Y - _previousPosition.Y);
            OnDragging(e);
        }

        private void StopDrag()
        {
            //Stop drag
            _isDragging = false;
            Popin(false); //implied popin, don't force
            //Drag stopped
            OnDragStopped();
        }
        #endregion

        #region IDisposable
        private bool disposed;

        // Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to take this object off the finalization queue
            // and prevent finalization code for this object from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and unmanaged resources can be disposed.
        // If disposing equals false, the method has been called by the runtime from inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed.
        private void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if(!disposed)
            {
                // If disposing equals true, dispose all managed and unmanaged resources.
                if(disposing)
                {
                    // Dispose managed resources.
                    Popin();
                }
                // Call the appropriate methods to clean up unmanaged resources here.
                // If disposing is false, only the following code is executed.
                // Note disposing has been done.
                disposed = true;
            }
        }

        // Use C# destructor syntax for finalization code.
        // This destructor will run only if the Dispose method does not get called.
        // It gives your base class the opportunity to finalize.
        // Do not provide destructors in types derived from this class.
        ~Dragable()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of readability and maintainability.
            Dispose(false);
        }
        #endregion
    }
}
